/*
 * Copyright (c) 2024 iSOFT INFRASTRUCTURE SOFTWARE CO., LTD.
 * easyAda is licensed under Mulan PubL v2.
 * You can use this software according to the terms and conditions of the Mulan PubL v2.
 * You may obtain a copy of Mulan PubL v2 at:
 *          http://license.coscl.org.cn/MulanPubL-2.0
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PubL v2 for more details.
 */

#ifndef ATOMIC_H
#define ATOMIC_H

#include <plat/config.h>
#include <arch/utils.h>

typedef struct atomic_t {
    volatile long data;
} atomic_t;

#define ATOMIC_INIT(n) \
    {                  \
        .data = (n)    \
    }

#ifndef CONFIG_NO_HARDWARE_PRIMITIVE_SUPPORT

#define ATOMIC_ADD(p, val)                     \
    ({                                         \
        long temp;                             \
        long ret;                              \
                                               \
        asm volatile("1:         \n"           \
                     "ldxr %0, [%2]\n"         \
                     "add  %0, %0, %3\n"       \
                     "stxr %w1, %0, [%2]\n"    \
                     "cbnz %w1, 1b\n\t"        \
                     : "=&r"(ret), "=&r"(temp) \
                     : "r"(p), "r"(val)        \
                     : "memory");              \
        ret;                                   \
    })
static inline long atomic_add(atomic_t *p, long val)
{
    return ATOMIC_ADD(p, val);
}

#define ATOMIC_SUB(p, val)                     \
    ({                                         \
        long temp;                             \
        long ret;                              \
                                               \
        asm volatile("1:         \n"           \
                     "ldxr %0, [%2]\n"         \
                     "sub  %0, %0, %3\n"       \
                     "stxr %w1, %0, [%2]\n"    \
                     "cbnz %w1, 1b\n\t"        \
                     : "=&r"(ret), "=&r"(temp) \
                     : "r"(p), "r"(val)        \
                     : "memory");              \
        ret;                                   \
    })
static inline long atomic_sub(atomic_t *p, long val)
{
    return ATOMIC_SUB(p, val);
}

#define ATOMIC_EXCHANGE(p, val)                \
    ({                                         \
        long temp;                             \
        long ret;                              \
                                               \
        asm volatile("dmb sy" ::: "memory");   \
        asm volatile("1:         \n"           \
                     "ldxr %0, [%2]\n\t"       \
                     "stxr %w1, %3, [%2]\n\t"  \
                     "cbnz %w1, 1b\n\t"        \
                     : "=&r"(ret), "=&r"(temp) \
                     : "r"(p), "r"(val)        \
                     : "memory");              \
        asm volatile("dmb sy" ::: "memory");   \
        ret;                                   \
    })
static inline long atomic_exchange(atomic_t *p, long val)
{
    return ATOMIC_EXCHANGE(p, val);
}

#define ATOMIC_COMPARE_EXCHANGE(p, old, new)      \
    ({                                            \
        long temp;                                \
        long ret;                                 \
                                                  \
        asm volatile("dmb sy" ::: "memory");      \
        asm volatile("1:         \n"              \
                     "ldxr %0,  [%2]\n\t"         \
                     "cmp  %0,  %4\n"             \
                     "b.ne      2f\n"             \
                     "stxr %w1, %3, [%2]\n\t"     \
                     "cbnz %w1, 1b\n\t"           \
                     "2:          \n"             \
                     : "=&r"(ret), "=&r"(temp)    \
                     : "r"(p), "r"(new), "r"(old) \
                     : "memory");                 \
        asm volatile("dmb sy" ::: "memory");      \
        ret;                                      \
    })
static inline long atomic_compare_exchange(atomic_t *p, long old, long new)
{
    return ATOMIC_COMPARE_EXCHANGE(p, old, new);
}

static inline long atomic_store(atomic_t *p, long val)
{
    (void)atomic_exchange(p, val);
    return val;
}

#define ATOMIC_GET(p)                          \
    ({                                         \
        long temp;                             \
        long ret;                              \
                                               \
        asm volatile("1:         \n"           \
                     "ldxr %0, [%2]\n"         \
                     "cmp  %0, 0\n"            \
                     "b.le     2f\n"           \
                     "sub  %0, %0, 1\n"        \
                     "stxr %w1, %0, [%2]\n"    \
                     "cbnz %w1, 1b\n"          \
                     "mov  %0, 1\n"            \
                     "b        3f\n"           \
                     "2:        \n"            \
                     "mov  %0, 0\n"            \
                     "3:        \n"            \
                     : "=&r"(ret), "=&r"(temp) \
                     : "r"(p)                  \
                     : "memory");              \
        ret;                                   \
    })
static inline long atomic_get(atomic_t *p)
{
    return ATOMIC_GET(p);
}

#define atomic_put(p, max) atomic_add(p, 1)
#define atomic_load(p)     ((p)->data)
#else
static inline long atomic_add(atomic_t *p, long val)
{
    p->data += val;
    return p->data;
}

static inline long atomic_sub(atomic_t *p, long val)
{
    p->data -= val;
    return p->data;
}

static inline long atomic_load(atomic_t *p)
{
    return p->data;
}

static inline long atomic_store(atomic_t *p, long val)
{
    p->data = val;
    return p->data;
}

static inline long atomic_get(atomic_t *p)
{
    if (p->data > 0) {
        p->data--;
        return 1;
    } else
        return 0;
}
#define atomic_put(p, max) atomic_add(p, 1)
static inline long atomic_exchange(atomic_t *p, long val)
{
    long ret;

    ret     = p->data;
    p->data = val;
    return ret;
}

static inline long atomic_compare_exchange(atomic_t *p, long old, long new)
{
    long ret;

    ret = p->data;
    if (ret == old)
        p->data = new;
    return ret;
}

#endif

#endif
